package com.author.document.editorkit;

import java.awt.event.ActionEvent;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import javax.swing.Action;
import javax.swing.JEditorPane;
import javax.swing.JTextPane;
import javax.swing.text.AbstractDocument;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.BoxView;
import javax.swing.text.DefaultEditorKit;
import javax.swing.text.DefaultStyledDocument;
import javax.swing.text.Document;
import javax.swing.text.Element;
import javax.swing.text.ElementIterator;
import javax.swing.text.SimpleAttributeSet;
import javax.swing.text.StyleConstants;
import javax.swing.text.StyledDocument;
import javax.swing.text.StyledEditorKit;
import javax.swing.text.View;
import javax.swing.text.ViewFactory;
import javax.swing.text.AbstractDocument.BranchElement;
import javax.swing.text.AbstractDocument.LeafElement;
import javax.swing.text.DefaultStyledDocument.ElementSpec;
import javax.swing.text.html.HTML;
import javax.swing.text.html.HTMLDocument;
import javax.swing.text.html.HTMLEditorKit;
import javax.swing.text.html.HTMLDocument.Iterator;
import javax.swing.text.html.StyleSheet;

import com.author.document.model.ExtendedDocument;
import com.author.document.view.ExtendedParagraphView;
import com.author.document.view.ListView;
import com.author.document.view.MultiPageParagraphView;
import com.author.document.view.MultiPageSectionView;
import com.author.surface.Surface;
import com.author.surface.TextPane;
import com.author.surface.TextPanel;

/**
 * This is the editor kit class of the Text Pane. The editor kit class
 * implements the ViewFactory based on which views are created as per their
 * corresponding element type.
 */
public class ExtendedEditorKit extends HTMLEditorKit {
	/** List Item HTML */
	public static final String	LIST_ITEM_HTML	= "<LI></LI>";

	// Element specs to be inserted at the end of an unordered/ordered list
	private List<ElementSpec>	specs;

	/** Create custom HTML Document model for the Author tool */
	public Document createDefaultDocument() {
		StyleSheet s = this.getStyleSheet();
		if (s != null) {
			return new ExtendedDocument(s);
		} else {
			throw new RuntimeException(
					"Stylesheet not initialized by HTML Editor Kit");
		}
	}

	@Override
	public ViewFactory getViewFactory() {
		return defaultViewFactory;
	}

	@Override
	public Action[] getActions() {
		Action[] res = super.getActions();
		for (int i = 0; i < res.length; i++) {
			if (insertBreakAction.equals(res[i].getValue(Action.NAME))) {
				res[i] = new BreakListAction(
						ExtendedEditorKit.insertBreakAction);
			}
		}
		return res;
	}

	private ViewFactory	defaultViewFactory	= new ExtendedEditorKit.ExtendedHTMLViewFactory();

	public static class ExtendedHTMLViewFactory extends
			HTMLEditorKit.HTMLFactory {
		@Override
		public View create(Element elem) {
			if ((elem.getName() == null) || (elem.getName() == "")) {
				return super.create(elem);
			}

			if ((elem.getAttributes()
					.getAttribute(StyleConstants.NameAttribute)
					.equals(HTML.Tag.P))
					|| (elem.getAttributes().getAttribute(
							StyleConstants.NameAttribute)
							.equals(HTML.Tag.IMPLIED))) {
				return new MultiPageParagraphView(elem);
			} else if (elem.getAttributes()
					.getAttribute(StyleConstants.NameAttribute)
					.equals(HTML.Tag.BODY)) {
				return new MultiPageSectionView(elem, View.Y_AXIS);
			}

			return super.create(elem);
		}
	}

	private class BreakListAction extends DefaultEditorKit.InsertBreakAction {
		public BreakListAction(String arg) {
			super();
		}

		@Override
		public void actionPerformed(ActionEvent ae) {
			/* Get reference of the the editor pane the current caret position */
			JEditorPane editor = (JEditorPane) this.getTextComponent(ae);

			/* Get the underlying HTML document model of the Text Pane */
			Surface surface = (Surface) Surface.getAppSurface();
			TextPanel textPanel = (TextPanel) surface.getTextPanel();
			TextPane textPane = (TextPane) textPanel.getTextPane();
			HTMLDocument doc = (HTMLDocument) textPane.getStyledDocument();

			Element root = doc.getDefaultRootElement();
			Element elem = null;
			int n = root.getElementCount();
			int offset = editor.getCaretPosition();

			for (int i = 0; i < n; i++) {
				elem = root.getElement(i);
				AttributeSet sas = elem.getAttributes();

				if (sas.getAttribute(StyleConstants.NameAttribute).equals(
						HTML.Tag.HEAD)) {
					continue; /* Ignore HEAD element */
				}

				while (!elem.isLeaf()) {
					int k = elem.getElementIndex(offset);
					if (k == -1) {
						break; /* Leaf element */
					}

					int m = elem.getElementCount() - 1;
					if ((k < 0) || (k > m)) {
						break; /* the element does not cover this position */
					}

					elem = elem.getElement(k);
					if (elem instanceof BranchElement) {
						break;
					}
				}

				if ((elem instanceof BranchElement)
						&& (offset >= elem.getStartOffset())
						&& (offset <= elem.getEndOffset())) {
					break;
				}
			}

			if (elem instanceof LeafElement) {
				return; /* Ignore Leaf element */
			}

			if (elem.getAttributes().getAttribute(StyleConstants.NameAttribute)
					.equals(HTML.Tag.UL)) {
				/* We are concerned only with the last child here */
				Element child = elem.getElement(elem.getElementCount() - 1);

				if (child.getAttributes()
						.getAttribute(StyleConstants.NameAttribute)
						.equals(HTML.Tag.LI)) {

					/*
					 * If this is a continuation of a list then insert another
					 * item
					 */
					if ((child.getEndOffset() - child.getStartOffset()) > 1) {
						try {
							doc.insertBeforeEnd(elem, LIST_ITEM_HTML);
							editor.setCaretPosition(doc.getLength() - 1);
						} catch (BadLocationException e) {
							e.printStackTrace();
						} catch (IOException e) {
							e.printStackTrace();
						}
					} else if (child.getEndOffset() - child.getStartOffset() == 1) {

						/*
						 * If the child element contains only one character then
						 * we interpret this as a scenario when the user has
						 * pressed the return key twice and wants to terminate
						 * the unordered list. So this will result in an
						 * insertion of a newline character which will then
						 * begin a new paragraph element
						 */

						// removeElementAndInsertNewline (editor, doc);

						try {
							offset = doc.getLength();
							doc.remove(offset - 1, 1);

							if (specs != null) {
								specs = null;
							}
							specs = new ArrayList<ElementSpec>();

							offset = doc.getLength();
							Element leafElement = ((HTMLDocument) doc)
									.getCharacterElement(offset); // leaf
																	// element
																	// at offset
							ElementSpec spec = null;

							spec = new ElementSpec(leafElement.getAttributes(),
									ElementSpec.ContentType,
									new char[] { '\n' }, offset + 1, 1);
							spec.setDirection(ElementSpec.JoinPreviousDirection);
							specs.add(spec);

							// spec = new ElementSpec
							// (leafElement.getAttributes(),
							// ElementSpec.EndTagType);
							// specs.add(spec);

							// spec = new ElementSpec
							// (leafElement.getAttributes(),
							// ElementSpec.EndTagType);
							// specs.add(spec);

							spec = new ElementSpec(leafElement.getAttributes(),
									ElementSpec.StartTagType);
							specs.add(spec);

							spec = new ElementSpec(leafElement.getAttributes(),
									ElementSpec.ContentType,
									new char[] { '\n' }, offset + 2, 1);
							specs.add(spec);

							/* Convert specs from ArrayList to array */
							ElementSpec[] specsArray = new ElementSpec[specs
									.size()];

							/*
							 * Batch insert the element specs into the document
							 * model
							 */
							// ((HTMLDocument) doc).insertString(offset, "\n",
							// null);
						} catch (Exception e) {
							e.printStackTrace();
						}
					}
				}
			} else if (elem.getAttributes()
					.getAttribute(StyleConstants.NameAttribute)
					.equals(HTML.Tag.OL)) {
				/* We are concerned only with the last child here */
				Element child = elem.getElement(elem.getElementCount() - 1);

				if (child.getAttributes()
						.getAttribute(StyleConstants.NameAttribute)
						.equals(HTML.Tag.LI)) {

					/*
					 * If this is a continuation of a list then insert another
					 * item
					 */
					if ((child.getEndOffset() - child.getStartOffset()) > 1) {
						try {
							doc.insertBeforeEnd(elem, LIST_ITEM_HTML);
							editor.setCaretPosition(doc.getLength() - 1);
						} catch (BadLocationException e) {
							e.printStackTrace();
						} catch (IOException e) {
							e.printStackTrace();
						}
					} else if (child.getEndOffset() - child.getStartOffset() == 1) {

						/*
						 * The following portion of the code is identical to the
						 * one above for unordered list. Please see the comments
						 * above
						 */
						removeElementAndInsertNewline(editor, doc);
					}
				}
			} else {
				editor.replaceSelection("\n");
			}
		}

		/*
		 * This will break an unordered/ordered list and insert a new line which
		 * will start a new paragraph element in the document model
		 */
		private void removeElementAndInsertNewline(JEditorPane editor,
				HTMLDocument doc) {
			try {
				/* Get the position of the last offset inside the document model */
				int offset = doc.getLength();
				doc.remove(offset - 1, 1);

				/*
				 * Find the element before whose end the new line character
				 * needs to be inserted. Typically this would be the BODY
				 * element of the HTML document
				 */

				Element bodyElem = null;
				Element root = doc.getDefaultRootElement();
				int n = root.getElementCount();

				for (int i = 0; i < n; i++) {
					Element tempElem = root.getElement(i);
					if (!(tempElem.getAttributes().getAttribute(
							StyleConstants.NameAttribute).equals(HTML.Tag.BODY))) {
						continue;
					}

					/* Found BODY element */
					bodyElem = tempElem;
					break;
				}

				if (bodyElem == null) {
					/* This shouldn't happen */
					return;
				}

				/*
				 * Insert new line before end of body element. This will
				 * generate a paragraph element in the HTML document
				 */
				doc.insertBeforeEnd(bodyElem, "\n");
				editor.setCaretPosition(doc.getLength());
			} catch (BadLocationException e) {
				e.printStackTrace();
			} catch (IOException ioe) {
				ioe.printStackTrace();
			}
		}
	}
}
